package io.adrainty.bolee.insurance.handler.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.util.IdcardUtil;
import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.collect.Lists;
import io.adrainty.bolee.framework.commons.basic.ResponseCode;
import io.adrainty.bolee.framework.commons.exception.AppException;
import io.adrainty.bolee.framework.commons.utils.JsonUtil;
import io.adrainty.bolee.insurance.constants.InsuranceConstant;
import io.adrainty.bolee.insurance.handler.AbstractInsuranceHandler;
import io.adrainty.bolee.insurance.models.aggregate.*;
import io.adrainty.bolee.insurance.models.entity.*;
import io.adrainty.bolee.insurance.models.request.DoInsuranceDTO;
import io.adrainty.bolee.insurance.models.valobj.*;
import org.apache.commons.lang3.ObjectUtils;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.*;

/**
 * 理财型：保险投保、创建订单、理财收益
 *
 * @author AdRainty
 * @version V1.0.0
 * @since 2025/8/25 下午11:22
 */
public abstract class EarningsInsuranceHandler extends AbstractInsuranceHandler {

    @Override
    public WarrantyEntity doInsure(DoInsuranceDTO doInsuranceDTO) {
        checkInsuranceData(doInsuranceDTO);
        InsuranceProcessDTO insuranceProcessDTO = buildInsureProcessDTO(
                doInsuranceDTO.getInsuranceId(), doInsuranceDTO.getInsurancePlanId(),
                doInsuranceDTO.getCompanyNo(), doInsuranceDTO.getInsuranceCoefficentIds());

        InsuranceEntity insuranceEntity = insuranceProcessDTO.getInsuranceEntity();
        InsurancePlanEntity insurancePlanEntity = insuranceProcessDTO.getInsurancePlanEntity();
        List<InsuranceCoefficientEntity> insuranceCoefficientList = insuranceProcessDTO.getInsuranceCoefficientList();
        // 投保人信息
        CustomerRelationEntity applicant = buildApplicant();
        // 被投保人信息
        CustomerRelationEntity insured = buildCustomerRelationList(doInsuranceDTO.getCustomerRelationIds())
                .stream().findFirst().orElseThrow(() -> new AppException(ResponseCode.INSURANCE_CALCULATE_ERROR, "请选择关系人"));

        checkBaseOnly(insuranceCoefficientList);
        checkRequireCoefficient(insuranceCoefficientList, InsuranceConstant.EARNING_CHECK_RULE);
        checkBuyMode(insuranceCoefficientList);
        checkInsuranceAge(insuranceEntity, insured);
        // 理财投保金额是否符合检测
        checkInsuranceAmount(insurancePlanEntity, insuranceCoefficientList, doInsuranceDTO.getPrice());
        String warrantyNo = String.valueOf(identifierGenerator.nextId(insuranceProcessDTO));

        EarningResultDTO earningResultDTO = earningsCompute(warrantyNo, applicant, doInsuranceDTO,
                insurancePlanEntity, insuranceCoefficientList, insured, false);
        Map<String, InsuranceCoefficientEntity> collect = buildCoefficientMap(insuranceCoefficientList);
        WarrantyTimeDTO warrantyTimeDTO = buildTimeSetup(doInsuranceDTO, collect, insuranceEntity);
        return WarrantyEntity.builder()
                .insuranceId(insuranceEntity.getId())
                .insuranceName(insuranceEntity.getInsuranceName())
                .insuranceJson(JsonUtil.toJson(insuranceProcessDTO))
                .warrantyNo(warrantyNo)
                .applicantName(applicant.getName())
                .applicantIdentityCard(applicant.getIdentityCard())
                .companyNo(insuranceProcessDTO.getCompanyEntity().getCompanyNo())
                .companyName(insuranceProcessDTO.getCompanyEntity().getCompanyName())
                .safeguardStartTime(warrantyTimeDTO.getSafeguardStartTime())
                .safeguardEndTime(warrantyTimeDTO.getSafeguardEndTime())
                .premium(doInsuranceDTO.getPrice())
                .premiums(earningResultDTO.getPremiums())
                .autoWarrantyExtension("0")
                .warrantyState(WarrantyStateEnum.WAIT_PAY)
                .underwritingState(UnderwritingStateEnum.UNDERWRITING_ING)
                .periods(earningResultDTO.getPeriods())
                .dutyPeriod(0)
                .hesitationTime(warrantyTimeDTO.getHesitationTime())
                .waitTime(warrantyTimeDTO.getWaitTime())
                .grace(insuranceEntity.getGrace())
                .graceUnit(insuranceEntity.getGraceUnit())
                .revival(insuranceEntity.getRevival())
                .revivalUnit(insuranceEntity.getRevivalUnit())
                .periodicUnit(earningResultDTO.getPeriodicUnit())
                .customerRelationVOs(Lists.newArrayList(insured))
                .build();
    }

    @Override
    public EarningResultDTO doEarnings(DoInsuranceDTO doInsuranceDTO) {
        checkInsuranceData(doInsuranceDTO);
        InsuranceProcessDTO insuranceProcessDTO = buildInsureProcessDTO(
                doInsuranceDTO.getInsuranceId(), doInsuranceDTO.getInsurancePlanId(),
                doInsuranceDTO.getCompanyNo(), doInsuranceDTO.getInsuranceCoefficentIds());

        InsuranceEntity insuranceEntity = insuranceProcessDTO.getInsuranceEntity();
        InsurancePlanEntity insurancePlanEntity = insuranceProcessDTO.getInsurancePlanEntity();
        List<InsuranceCoefficientEntity> insuranceCoefficientList = insuranceProcessDTO.getInsuranceCoefficientList();
        // 投保人信息
        CustomerRelationEntity applicant = buildApplicant();
        // 被投保人信息
        CustomerRelationEntity insured = buildCustomerRelationList(doInsuranceDTO.getCustomerRelationIds())
                .stream().findFirst().orElseThrow(() -> new AppException(ResponseCode.INSURANCE_CALCULATE_ERROR, "请选择关系人"));

        checkBaseOnly(insuranceCoefficientList);
        checkRequireCoefficient(insuranceCoefficientList, InsuranceConstant.EARNING_CHECK_RULE);
        checkBuyMode(insuranceCoefficientList);
        checkInsuranceAge(insuranceEntity, insured);
        // 理财投保金额是否符合检测
        checkInsuranceAmount(insurancePlanEntity, insuranceCoefficientList, doInsuranceDTO.getPrice());

        return earningsCompute(null, applicant, doInsuranceDTO, insurancePlanEntity, insuranceCoefficientList, insured, true);
    }

    /**
     * 理财收益试算
     *
     * @param warrantyNo               合同编号
     * @param applicant                投保人信息
     * @param doInsuranceDTO           投保参数
     * @param insurancePlanEntity      理财计划
     * @param insuranceCoefficientList 理财系数
     * @param insured                  被投保人信息
     * @param isTrial                  是否试算
     * @return 理财收益试算结果
     */
    private EarningResultDTO earningsCompute(String warrantyNo, CustomerRelationEntity applicant, DoInsuranceDTO doInsuranceDTO,
                                             InsurancePlanEntity insurancePlanEntity, List<InsuranceCoefficientEntity> insuranceCoefficientList,
                                             CustomerRelationEntity insured, boolean isTrial) {
        Map<String, InsuranceCoefficientEntity> collect = buildCoefficientMap(insuranceCoefficientList);

        // 系数：投入方式
        JsonAttributeDTO jsonAttribute = getJsonAttribute(collect, InsuranceConstant.BUY_MODE);
        // 投入周期时长
        BigDecimal periodic;
        // 投入周期单位
        String periodicUnit;
        // 投入总周期数
        Integer periods;
        // 投入总金额
        BigDecimal premiums;
        if (InsuranceConstant.BUY_MODE_0.equals(jsonAttribute.getVal())) {
            // 找到投入周期时长系数转换为JsonAttribute获取CalculatedVal
            periodic = new BigDecimal(getJsonAttribute(collect, InsuranceConstant.PERIODIC).getCalculatedVal());
            // 找到投入周期时长系数转换为JsonAttribute获取Val
            periodicUnit = getJsonAttribute(collect, InsuranceConstant.PERIODIC).getVal();
            // 找投入周期时长X投入周期频率：例如每月投，投入3年计算方式2X12=36期
            periods = doPeriods(collect);
            // 投入周期时长X传入金额
            premiums = doInsuranceDTO.getPrice().multiply(new BigDecimal(periods));
        } else {
            periodic = BigDecimal.ZERO;
            periodicUnit = "ALL-IN";
            periods = 1;
            premiums = doInsuranceDTO.getPrice();
        }
        // 投入结束时间
        LocalDateTime putInEndTime = LocalDateTimeUtil.offset(LocalDateTime.now(), periodic.longValue(), ChronoUnit.YEARS);
        // 领取周期单位
        String actualGetPeriodicUnit = getJsonAttribute(collect, InsuranceConstant.ACTUAL_GET_UNIT).getVal();
        // 领取起始时间
        LocalDateTime actualGetStartTime = doActualGetStartTime(collect, insured);
        // 理财影响系数:影响最终收益的系数
        BigDecimal premiumsHandler = doPremiumsHandler(insuranceCoefficientList, premiums);
        // 领取计划
        List<EarningPeriodicDTO> periodicVos = doPeriodicList(warrantyNo, applicant, insured, insurancePlanEntity, premiumsHandler,
                collect, actualGetPeriodicUnit, actualGetStartTime, isTrial, doInsuranceDTO);
        // 领取总金额
        BigDecimal receivedAmounts = periodicVos.stream()
                .map(EarningPeriodicDTO::getReceivedAmount)
                .reduce(BigDecimal.ZERO, BigDecimal::add);
        return EarningResultDTO.builder()
                .premium(doInsuranceDTO.getPrice())
                .premiums(premiums)
                .periodic(periodic.toPlainString())
                .periods(periods)
                .periodicUnit(periodicUnit)
                .actualGetStartTime(actualGetStartTime)
                .putInEndTime(putInEndTime)
                .actualGetPeriodicUnit(actualGetPeriodicUnit)
                .periodicVos(periodicVos)
                .accumulatedEarnings(receivedAmounts.subtract(premiums))
                .receivedAmounts(receivedAmounts.setScale(2, RoundingMode.HALF_UP))
                .multiple(receivedAmounts.divide(premiums, 2, RoundingMode.HALF_UP))
                .build();
    }

    private List<EarningPeriodicDTO> doPeriodicList(String warrantyNo, CustomerRelationEntity applicant, CustomerRelationEntity insured,
                                                    InsurancePlanEntity insurancePlanEntity, BigDecimal premiums, Map<String, InsuranceCoefficientEntity> collect,
                                                    String actualGetPeriodicUnit, LocalDateTime actualGetStartTime, boolean isTrial, DoInsuranceDTO doInsuranceDTO) {
        // 领取计划存储
        List<EarningPeriodicDTO> periodicVos = new ArrayList<>();
        // 保险给付计划
        PlanEarningsEntity planEarningsVO = insurancePlanEntity.getPlanEarningsVO();
        // 此处定义的jsonAttributes的val为每年领取比例
        List<JsonAttributeDTO> jsonAttributes = JsonUtil.fromJson(planEarningsVO.getEarningsJson(), new TypeReference<>() {
        });
        // 终身领取型
        if (EarningTypeEnum.LZL.getCode().equals(planEarningsVO.getEarningsType())) {
            // 养老试算时，用户指定测算时间, 非养老试算时，合同规定截止时间，领至终身假定150岁
            String key = (isTrial && CheckRuleEnum.ELDERLY.getCode().equals(doInsuranceDTO.getCheckRule())) ?
                    InsuranceConstant.TRIAL_GET_END : InsuranceConstant.ACTUAL_GET_END;
            JsonAttributeDTO jsonAttribute = getJsonAttribute(collect, key);
            // 计算领取总年数=领取结束时间-领取开始时间
            long actualGetYear = 0L;
            if (InsuranceConstant.YEAR.equals(jsonAttribute.getUnit())) {
                // 领取截止时间
                LocalDateTime actualGetEndTime = LocalDateTimeUtil.offset(LocalDateTime.now(),
                        Long.parseLong(jsonAttribute.getVal()), ChronoUnit.YEARS);
                actualGetYear = LocalDateTimeUtil.between(actualGetStartTime, actualGetEndTime, ChronoUnit.YEARS);
            }
            if (InsuranceConstant.AGE.equals(jsonAttribute.getUnit())) {
                // 被投保人当前年龄
                int age = IdcardUtil.getAgeByIdCard(insured.getIdentityCard(), new Date());
                // 领取截止时间
                LocalDateTime actualGetEndTime = LocalDateTimeUtil.offset(LocalDateTime.now(),
                        Long.parseLong(jsonAttribute.getVal()) - age, ChronoUnit.YEARS);
                actualGetYear = LocalDateTimeUtil.between(actualGetStartTime, actualGetEndTime, ChronoUnit.YEARS);
            }
            // 按月领取
            if (InsuranceConstant.MONTH.equals(actualGetPeriodicUnit)) {
                actualGetYear = actualGetYear * 12;
                BigDecimal flag = new BigDecimal(jsonAttributes.get(0).getVal())
                        .divide(new BigDecimal(12), 2, RoundingMode.HALF_UP)
                        .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
                for (int i = 0; i < actualGetYear; i++) {
                    EarningPeriodicDTO periodicVo = new EarningPeriodicDTO();
                    periodicVo.setPeriodic("第" + (i + 1) + "期");
                    periodicVo.setActualGetStartTime(LocalDateTimeUtil.offset(actualGetStartTime, i, ChronoUnit.MONTHS));
                    periodicVo.setReceivedAmount(premiums.multiply(flag));
                    periodicVos.add(periodicVo);
                }
            }
            // 按年领取
            if (InsuranceConstant.YEAR.equals(actualGetPeriodicUnit)) {
                BigDecimal flag = new BigDecimal(jsonAttributes.get(0).getVal())
                        .divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
                for (int i = 0; i < actualGetYear; i++) {
                    EarningPeriodicDTO periodicVo = new EarningPeriodicDTO();
                    periodicVo.setPeriodic("第" + (i + 1) + "期");
                    periodicVo.setActualGetStartTime(LocalDateTimeUtil.offset(actualGetStartTime, i, ChronoUnit.YEARS));
                    periodicVo.setReceivedAmount(premiums.multiply(flag));
                    periodicVos.add(periodicVo);
                }
            }
        }
        // 固定期限领取
        if (EarningTypeEnum.FIXED.getCode().equals(planEarningsVO.getEarningsType())) {
            for (int i = 0; i < jsonAttributes.size(); i++) {
                BigDecimal flag = new BigDecimal(jsonAttributes.get(i).getVal()).divide(new BigDecimal(100), 2, RoundingMode.HALF_UP);
                EarningPeriodicDTO periodicVo = new EarningPeriodicDTO();
                periodicVo.setPeriodic(jsonAttributes.get(i).getName());
                periodicVo.setReceivedAmount(premiums.multiply(flag));
                periodicVo.setActualGetStartTime(LocalDateTimeUtil.offset(actualGetStartTime, i, ChronoUnit.YEARS));
                periodicVos.add(periodicVo);
            }
        }
        // 非试算，需要保存给付计划订单
        if (!isTrial && CollUtil.isNotEmpty(periodicVos)) {
            List<WarrantyEarningsOrderEntity> warrantyEarningsOrderList = periodicVos.stream().map(n ->
                    WarrantyEarningsOrderEntity.builder()
                            .scheduleTime(n.getActualGetStartTime())
                            .applicantIdentityCard(applicant.getIdentityCard())
                            .applicantName(applicant.getName())
                            .currentPeriod(n.getPeriodic())
                            .orderNo(String.valueOf(identifierGenerator.nextId(periodicVos)))
                            .orderState(OrderStateEnum.WAITING_PAY.getCode())
                            .warrantyNo(warrantyNo)
                            .premium(n.getReceivedAmount())
                            .build()
            ).toList();
            warrantyRepository.saveEarningsOrderBatch(warrantyEarningsOrderList);
        }
        return periodicVos;
    }

    /**
     * 理财收益试算领取计划
     *
     * @param coefficientEntities 理财系数
     * @param premiums            原始保费
     */
    private BigDecimal doPremiumsHandler(List<InsuranceCoefficientEntity> coefficientEntities, BigDecimal premiums) {
        BigDecimal sumScore = coefficientEntities.stream()
                .map(n -> ObjectUtils.defaultIfNull(n.getScore(), BigDecimal.ZERO))
                .reduce(BigDecimal.ZERO, BigDecimal::add).add(BigDecimal.ONE);
        return premiums.multiply(sumScore);
    }

    /**
     * 理财投保金额是否符合检测
     *
     * @param insurancePlanEntity      理财计划
     * @param insuranceCoefficientList 理财系数
     * @param price                    投保金额
     */
    private void checkInsuranceAmount(InsurancePlanEntity insurancePlanEntity,
                                      List<InsuranceCoefficientEntity> insuranceCoefficientList, BigDecimal price) {
        // 投入方式
        Map<String, InsuranceCoefficientEntity> collect = buildCoefficientMap(insuranceCoefficientList);
        InsuranceCoefficientEntity buyMode = collect.get(InsuranceConstant.BUY_MODE);
        // 趸交
        JsonAttributeDTO jsonBuyMode = JsonUtil.fromJson(buyMode.getCoefficientValue(), JsonAttributeDTO.class);
        if (InsuranceConstant.BUY_MODE_1.equals(jsonBuyMode.getVal())) {
            if (insurancePlanEntity.getMaxPriceAllIn().compareTo(price) <= 0) {
                throw new AppException(ResponseCode.INSURANCE_CALCULATE_ERROR, "输入金额不符合要求");
            }
            return;
        }
        // 定投
        boolean result;
        InsuranceCoefficientEntity periodicUnit = collect.get(InsuranceConstant.PERIODIC_UNIT);
        JsonAttributeDTO jsonPeriodicUnit = JsonUtil.fromJson(periodicUnit.getCoefficientValue(), JsonAttributeDTO.class);
        // 单位判定
        if (InsuranceConstant.WEEK.equals(jsonPeriodicUnit.getVal())) {
            result = insurancePlanEntity.getMaxPriceWeek().compareTo(price) > 0;
        } else if (InsuranceConstant.MONTH.equals(jsonPeriodicUnit.getVal())) {
            result = insurancePlanEntity.getMaxPriceMonth().compareTo(price) > 0;
        } else if (InsuranceConstant.YEAR.equals(jsonPeriodicUnit.getVal())) {
            result = insurancePlanEntity.getMaxPriceYear().compareTo(price) > 0;
        } else {
            result = false;
        }

        if (!result) {
            throw new AppException(ResponseCode.INSURANCE_CALCULATE_ERROR, "输入金额不符合要求");
        }
    }

    /**
     * 校验数据
     *
     * @param doInsureVo 投保参数
     */
    protected void checkInsuranceData(DoInsuranceDTO doInsureVo) {
        if (doInsureVo.getCustomerRelationIds().size() > 1) {
            throw new AppException(ResponseCode.INSURANCE_CALCULATE_ERROR, "不支持团险");
        }
        if (Objects.nonNull(doInsureVo.getSafeguardStartTime())) {
            throw new AppException(ResponseCode.INSURANCE_CALCULATE_ERROR, "不支持自定义保单起保时间");
        }
    }

    @Override
    public String doPremium(DoInsuranceDTO doInsureVo) {
        return ILLEGAL_PREMIUM;
    }

    /**
     * 领取开始时间
     *
     * @param collect 系数
     * @param insured 被保人
     */
    protected LocalDateTime doActualGetStartTime(Map<String, InsuranceCoefficientEntity> collect, CustomerRelationEntity insured) {
        // 领取开始系数
        JsonAttributeDTO jsonActualGet = getJsonAttribute(collect, InsuranceConstant.ACTUAL_GET_START);
        BigDecimal compoundAnnualInterest = BigDecimal.ZERO;
        // 多少年后开始领取
        if (InsuranceConstant.YEAR.equals(jsonActualGet.getUnit())) {
            compoundAnnualInterest = new BigDecimal(jsonActualGet.getCalculatedVal());
        }
        // 多少岁后开始领取
        if (InsuranceConstant.AGE.equals(jsonActualGet.getUnit())) {
            // 被投保人当前年龄
            int age = IdcardUtil.getAgeByIdCard(insured.getIdentityCard(), new Date());
            compoundAnnualInterest = new BigDecimal(jsonActualGet.getCalculatedVal()).subtract(new BigDecimal(age));
        }
        return LocalDateTimeUtil.offset(LocalDateTime.now(), compoundAnnualInterest.longValue(), ChronoUnit.YEARS);
    }

}
